<?php

// +-----------------------------------------------------------------------+

// | Copyright (c) 2002-2004, Richard Heyes                                |

// | All rights reserved.                                                  |

// |                                                                       |

// | Redistribution and use in source and binary forms, with or without    |

// | modification, are permitted provided that the following conditions    |

// | are met:                                                              |

// |                                                                       |

// | o Redistributions of source code must retain the above copyright      |

// |   notice, this list of conditions and the following disclaimer.       |

// | o Redistributions in binary form must reproduce the above copyright   |

// |   notice, this list of conditions and the following disclaimer in the |

// |   documentation and/or other materials provided with the distribution.|

// | o The names of the authors may not be used to endorse or promote      |

// |   products derived from this software without specific prior written  |

// |   permission.                                                         |

// |                                                                       |

// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |

// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |

// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |

// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |

// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |

// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |

// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |

// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |

// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |

// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |

// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |

// |                                                                       |

// +-----------------------------------------------------------------------+

// | Author: Richard Heyes <richard at php net>                            |

// +-----------------------------------------------------------------------+

//

// $Id: URL.php,v 1.36 2004/06/19 18:58:50 richard Exp $

//

// Net_URL Class



class Net_URL

{

    /**

    * Full url

    * @var string

    */

    var $url;



    /**

    * Protocol

    * @var string

    */

    var $protocol;



    /**

    * Username

    * @var string

    */

    var $username;



    /**

    * Password

    * @var string

    */

    var $password;



    /**

    * Host

    * @var string

    */

    var $host;



    /**

    * Port

    * @var integer

    */

    var $port;



    /**

    * Path

    * @var string

    */

    var $path;



    /**

    * Query string

    * @var array

    */

    var $querystring;



    /**

    * Anchor

    * @var string

    */

    var $anchor;



    /**

    * Whether to use []

    * @var bool

    */

    var $useBrackets;



    /**

    * PHP4 Constructor

    *

    * @see __construct()

    */

    function Net_URL($url = null, $useBrackets = true)

    {

        $this->__construct($url, $useBrackets);

    }



    /**

    * PHP5 Constructor

    *

    * Parses the given url and stores the various parts

    * Defaults are used in certain cases

    *

    * @param string $url         Optional URL

    * @param bool   $useBrackets Whether to use square brackets when

    *                            multiple querystrings with the same name

    *                            exist

    */

    function __construct($url = null, $useBrackets = true)

    {

        $_SERVER  = !empty($_SERVER) ? $_SERVER : $GLOBALS['_SERVER'];



        $this->useBrackets = $useBrackets;

        $this->url         = $url;

        $this->user        = '';

        $this->pass        = '';

        $this->host        = '';

        $this->port        = 80;

        $this->path        = '';

        $this->querystring = array();

        $this->anchor      = '';



        // Only use defaults if not an absolute URL given

        if (!preg_match('/^[a-z0-9]+:\/\//i', $url)) {



            $this->protocol    = (@$_SERVER['HTTPS'] == 'on' ? 'https' : 'http');



            /**

            * Figure out host/port

            */

            if (!empty($_SERVER['HTTP_HOST']) AND preg_match('/^(.*)(:([0-9]+))?$/U', $_SERVER['HTTP_HOST'], $matches)) {

                $host = $matches[1];

                if (!empty($matches[3])) {

                    $port = $matches[3];

                } else {

                    $port = $this->getStandardPort($this->protocol);

                }

            }



            $this->user        = '';

            $this->pass        = '';

            $this->host        = !empty($host) ? $host : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');

            $this->port        = !empty($port) ? $port : (isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : $this->getStandardPort($this->protocol));

            $this->path        = !empty($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '/';

            $this->querystring = isset($_SERVER['QUERY_STRING']) ? $this->_parseRawQuerystring($_SERVER['QUERY_STRING']) : null;

            $this->anchor      = '';

        }



        // Parse the url and store the various parts

        if (!empty($url)) {

            $urlinfo = parse_url($url);



            // Default querystring

            $this->querystring = array();



            foreach ($urlinfo as $key => $value) {

                switch ($key) {

                    case 'scheme':

                        $this->protocol = $value;

                        $this->port     = $this->getStandardPort($value);

                        break;



                    case 'user':

                    case 'pass':

                    case 'host':

                    case 'port':

                        $this->$key = $value;

                        break;



                    case 'path':

                        if ($value{0} == '/') {

                            $this->path = $value;

                        } else {

                            $path = dirname($this->path) == DIRECTORY_SEPARATOR ? '' : dirname($this->path);

                            $this->path = sprintf('%s/%s', $path, $value);

                        }

                        break;



                    case 'query':

                        $this->querystring = $this->_parseRawQueryString($value);

                        break;



                    case 'fragment':

                        $this->anchor = $value;

                        break;

                }

            }

        }

    }



    /**

    * Returns full url

    *

    * @return string Full url

    * @access public

    */

    function getURL()

    {

        $querystring = $this->getQueryString();



        $this->url = $this->protocol . '://'

                   . $this->user . (!empty($this->pass) ? ':' : '')

                   . $this->pass . (!empty($this->user) ? '@' : '')

                   . $this->host . ($this->port == $this->getStandardPort($this->protocol) ? '' : ':' . $this->port)

                   . $this->path

                   . (!empty($querystring) ? '?' . $querystring : '')

                   . (!empty($this->anchor) ? '#' . $this->anchor : '');



        return $this->url;

    }



    /**

    * Adds a querystring item

    *

    * @param  string $name       Name of item

    * @param  string $value      Value of item

    * @param  bool   $preencoded Whether value is urlencoded or not, default = not

    * @access public

    */

    function addQueryString($name, $value, $preencoded = false)

    {

        if ($preencoded) {

            $this->querystring[$name] = $value;

        } else {

            $this->querystring[$name] = is_array($value) ? array_map('rawurlencode', $value): rawurlencode($value);

        }

    }



    /**

    * Removes a querystring item

    *

    * @param  string $name Name of item

    * @access public

    */

    function removeQueryString($name)

    {

        if (isset($this->querystring[$name])) {

            unset($this->querystring[$name]);

        }

    }



    /**

    * Sets the querystring to literally what you supply

    *

    * @param  string $querystring The querystring data. Should be of the format foo=bar&x=y etc

    * @access public

    */

    function addRawQueryString($querystring)

    {

        $this->querystring = $this->_parseRawQueryString($querystring);

    }



    /**

    * Returns flat querystring

    *

    * @return string Querystring

    * @access public

    */

    function getQueryString()

    {

        if (!empty($this->querystring)) {

            foreach ($this->querystring as $name => $value) {

                if (is_array($value)) {

                    foreach ($value as $k => $v) {

                        $querystring[] = $this->useBrackets ? sprintf('%s[%s]=%s', $name, $k, $v) : ($name . '=' . $v);

                    }

                } elseif (!is_null($value)) {

                    $querystring[] = $name . '=' . $value;

                } else {

                    $querystring[] = $name;

                }

            }

            $querystring = implode(ini_get('arg_separator.output'), $querystring);

        } else {

            $querystring = '';

        }



        return $querystring;

    }



    /**

    * Parses raw querystring and returns an array of it

    *

    * @param  string  $querystring The querystring to parse

    * @return array                An array of the querystring data

    * @access private

    */

    function _parseRawQuerystring($querystring)

    {

        $parts  = preg_split('/[' . preg_quote(ini_get('arg_separator.input'), '/') . ']/', $querystring, -1, PREG_SPLIT_NO_EMPTY);

        $return = array();



        foreach ($parts as $part) {

            if (strpos($part, '=') !== false) {

                $value = substr($part, strpos($part, '=') + 1);

                $key   = substr($part, 0, strpos($part, '='));

            } else {

                $value = null;

                $key   = $part;

            }

            if (substr($key, -2) == '[]') {

                $key = substr($key, 0, -2);

                if (@!is_array($return[$key])) {

                    $return[$key]   = array();

                    $return[$key][] = $value;

                } else {

                    $return[$key][] = $value;

                }

            } elseif (!$this->useBrackets AND !empty($return[$key])) {

                $return[$key]   = (array)$return[$key];

                $return[$key][] = $value;

            } else {

                $return[$key] = $value;

            }

        }



        return $return;

    }



    /**

    * Resolves //, ../ and ./ from a path and returns

    * the result. Eg:

    *

    * /foo/bar/../boo.php    => /foo/boo.php

    * /foo/bar/../../boo.php => /boo.php

    * /foo/bar/.././/boo.php => /foo/boo.php

    *

    * This method can also be called statically.

    *

    * @param  string $url URL path to resolve

    * @return string      The result

    */

    function resolvePath($path)

    {

        $path = explode('/', str_replace('//', '/', $path));



        for ($i=0; $i<count($path); $i++) {

            if ($path[$i] == '.') {

                unset($path[$i]);

                $path = array_values($path);

                $i--;



            } elseif ($path[$i] == '..' AND ($i > 1 OR ($i == 1 AND $path[0] != '') ) ) {

                unset($path[$i]);

                unset($path[$i-1]);

                $path = array_values($path);

                $i -= 2;



            } elseif ($path[$i] == '..' AND $i == 1 AND $path[0] == '') {

                unset($path[$i]);

                $path = array_values($path);

                $i--;



            } else {

                continue;

            }

        }



        return implode('/', $path);

    }



    /**

    * Returns the standard port number for a protocol

    *

    * @param  string  $scheme The protocol to lookup

    * @return integer         Port number or NULL if no scheme matches

    *

    * @author Philippe Jausions <Philippe.Jausions@11abacus.com>

    */

    function getStandardPort($scheme)

    {

        switch (strtolower($scheme)) {

            case 'http':    return 80;

            case 'https':   return 443;

            case 'ftp':     return 21;

            case 'imap':    return 143;

            case 'imaps':   return 993;

            case 'pop3':    return 110;

            case 'pop3s':   return 995;

            default:        return null;

       }

    }



    /**

    * Forces the URL to a particular protocol

    *

    * @param string  $protocol Protocol to force the URL to

    * @param integer $port     Optional port (standard port is used by default)

    */

    function setProtocol($protocol, $port = null)

    {

        $this->protocol = $protocol;

        $this->port = is_null($port) ? $this->getStandardPort() : $port;

    }



}

?>

